package com.sanss.trapParse.trap;

import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.Properties;
import java.util.Vector;

import org.apache.log4j.Logger;
import org.snmp4j.CommandResponder;
import org.snmp4j.CommandResponderEvent;
import org.snmp4j.MessageDispatcherImpl;
import org.snmp4j.PDU;
import org.snmp4j.PDUv1;
import org.snmp4j.Snmp;
import org.snmp4j.TransportMapping;
import org.snmp4j.mp.MPv1;
import org.snmp4j.mp.MPv2c;
import org.snmp4j.mp.MPv3;
import org.snmp4j.security.SecurityModels;
import org.snmp4j.security.SecurityProtocols;
import org.snmp4j.security.USM;
import org.snmp4j.smi.Address;
import org.snmp4j.smi.GenericAddress;
import org.snmp4j.smi.OctetString;
import org.snmp4j.smi.TcpAddress;
import org.snmp4j.smi.UdpAddress;
import org.snmp4j.smi.VariableBinding;
import org.snmp4j.transport.DefaultTcpTransportMapping;
import org.snmp4j.transport.DefaultUdpTransportMapping;
import org.snmp4j.util.MultiThreadedMessageDispatcher;
import org.snmp4j.util.ThreadPool;

import com.sanss.trapParse.info.*;
import com.sanss.trapParse.xml.XmlParser;

public class TrapReceiver implements CommandResponder {

	private String address;
	private String mibCfgFile;
	private String ruleDir;
	private String location;
	private Logger logger;
	private String heartBeatCfg;
	private int checkCycle;
	private HeartBeatHandler hbh;
	private ArrayList<HeartbeatInfo> al_hb;

	private static final int OID_POSITION_IN_TRAP_V2 = 1;// determine the trap
	// oid

	private MultiThreadedMessageDispatcher dispatcher;
	private Snmp snmp = null;
	private Address listenAddress;
	private ThreadPool threadPool;

	public TrapReceiver(String address, String mibCfgFile, String ruleDir,
			String location, String heartBeatCfg, int checkCycle, Logger logger) {
		this.address = address;
		this.mibCfgFile = mibCfgFile;
		this.ruleDir = ruleDir;
		this.location = location;
		this.logger = logger;
		this.heartBeatCfg = heartBeatCfg;
		this.checkCycle = checkCycle;
		al_hb = new ArrayList<HeartbeatInfo>();
	}

	private void init() throws UnknownHostException, IOException {
		threadPool = ThreadPool.create("Trap", 100);// pool size 100
		dispatcher = new MultiThreadedMessageDispatcher(threadPool,
				new MessageDispatcherImpl());
		listenAddress = GenericAddress.parse(address);
		TransportMapping transport;

		if (listenAddress instanceof UdpAddress) {
			transport = new DefaultUdpTransportMapping(
					(UdpAddress) listenAddress);
		} else {
			transport = new DefaultTcpTransportMapping(
					(TcpAddress) listenAddress);
		}

		snmp = new Snmp(dispatcher, transport);
		snmp.getMessageDispatcher().addMessageProcessingModel(new MPv1());
		snmp.getMessageDispatcher().addMessageProcessingModel(new MPv2c());
		snmp.getMessageDispatcher().addMessageProcessingModel(new MPv3());
		USM usm = new USM(SecurityProtocols.getInstance(), new OctetString(
				MPv3.createLocalEngineID()), 0);
		SecurityModels.getInstance().addSecurityModel(usm);
		snmp.listen();
		logger.info("start listening...");
	}

	public void run() {
		try {
			init();
			initHeartbeat();// prepare for heartbeat handling
			snmp.addCommandResponder(this);
		} catch (Exception e) {
			e.printStackTrace();
			logger.error("Here encountered an error!");
			logger.error(e);
		}
	}

	public void processPdu(CommandResponderEvent respEvnt) {
		try {
			TrapInfo ti = new TrapInfo();
			if (respEvnt != null && respEvnt.getPDU() != null) {
				logger.info("received a trap");
				// get ip and port of trap agent
				ti.setSender(respEvnt.getPeerAddress().toString());
				PDU rcvPDU = respEvnt.getPDU();
				if (rcvPDU.getType() == PDU.V1TRAP) {// smiv1 trap
					ti.setVersion(PDU.V1TRAP);
					PDUv1 v1trap = (PDUv1) rcvPDU;
					ti.setGenericTrap(v1trap.getGenericTrap());
					ti.setSpecificTrap(v1trap.getSpecificTrap());
					String oid = v1trap.getEnterprise().toString();
					// for debug
					logger.debug("-----------------------");
					logger.debug("enterprise: " + oid);
					logger.debug("agent-addr: " + v1trap.getAgentAddress());
					logger.debug("generic-trap: " + ti.getGenericTrap());
					logger.debug("specific-trap: " + ti.getSpecTrap());
					logger.debug("timestamp: " + v1trap.getTimestamp());
					logger.debug(v1trap.getVariableBindings().toString());
					logger.debug("-----------------------");

					Vector<VariableBinding> recVBs = v1trap
							.getVariableBindings();
					for (int i = 0; i < recVBs.size(); i++) {
						TrapPDU trapPDU = new TrapPDU();
						VariableBinding recVB = recVBs.elementAt(i);
						trapPDU.setOID(recVB.getOid());
						trapPDU.setVariable(recVB.getVariable());
						// add oid and contents to trapinfo
						ti.addTrapPDU(trapPDU);
					}
				} else {// smiv2 trap
					// get trap PDU
					Vector<VariableBinding> recVBs = respEvnt.getPDU()
							.getVariableBindings();
					for (int i = 0; i < recVBs.size(); i++) {// process trap PDU
						TrapPDU trapPDU = new TrapPDU();
						VariableBinding recVB = recVBs.elementAt(i);
						trapPDU.setOID(recVB.getOid());
						trapPDU.setVariable(recVB.getVariable());
						// add oid and contents to trapinfo
						ti.addTrapPDU(trapPDU);
					}
					ti.setVersion(PDU.TRAP);
					// System.out.println(OutputText(ti));

					// for debug
					logger.debug("-----------------------");
					for (TrapPDU tpdu : ti.getTrapPDU()) {
						logger.debug("oid: " + tpdu.getOID() + "\tvalue: "
								+ tpdu.getVariable());
					}
					logger.debug("-----------------------");
				}

				int expireSec = checkHeartbeat(ti);
				if (expireSec > 0) {// handle heart beat
					hbh.updateHeartbeatStatus(ti, expireSec);
				} else {// handle trap
					TrapHandler th = new TrapHandler(ti, mibCfgFile, ruleDir,
							location, logger);
					th.HandleTrap();
				}
			}
		} catch (Exception e) {
			e.printStackTrace();
			logger.error("!!!!!!!!!!!!!!!!!!!!");
			logger.error(e);
			logger.info("--------content of this trap--------");
			logger.info(respEvnt.getPDU().getVariableBindings().toString());
			logger.info("----------end of this trap----------");
			logger.error("!!!!!!!!!!!!!!!!!!!!");
		}
	}

	private FileInputStream xmlLocator(String oidIn) throws IOException {
		String oid = oidIn.substring(0, oidIn.length() - 2);// ignore ".0" tail
		// read config file, get coresponding xml file name
		Properties conf = new Properties();
		FileInputStream fis = new FileInputStream(mibCfgFile);
		conf.load(fis);
		fis.close();
		String mibFile = ruleDir + "/" + conf.getProperty(oid).trim() + ".xml";
		FileInputStream mibFIS = new FileInputStream(mibFile);
		return mibFIS;
	}

	/* for debug */
	private String OutputText(TrapInfo trapInfo) {
		StringBuilder analysis = new StringBuilder("");
		// String analysis = "";
		try {
			for (TrapPDU tpdu : trapInfo.getTrapPDU()) {
				String oid = tpdu.getOID();
				FileInputStream mibFIS = xmlLocator(oid);
				XmlParser xp = new XmlParser(oid);
				MibInfo mi = xp.Analysis(mibFIS);
				if (mi == null)
					System.out.println("null");
				else {
					analysis.append("Agent: ").append(trapInfo.getSender())
							.append(", ").append(mi.getDescription())
							.append(", ").append("Value: ")
							.append(tpdu.getVariable()).append("\n");
					// analysis += "Agent: " + trapInfo.getSender() + ", "
					// + mi.getDescription() + ", " + "Value: "
					// + tpdu.getVariable() + "\n";
				}
			}
		} catch (Exception e) {
			logger.error(e);
			return "";
		}
		return analysis.toString();
	}

	private void getHearbeatConfig() throws IOException {
		FileInputStream fis = new FileInputStream(heartBeatCfg);
		BufferedReader br = new BufferedReader(new InputStreamReader(fis));
		String line = null;// line reader
		while ((line = br.readLine()) != null) {
			line = line.trim();
			if (line.trim().equals(""))
				continue;
			if (line.startsWith("#"))
				continue;
			HeartbeatInfo hbi = new HeartbeatInfo();
			hbi.setOid(line.split("\\=")[0].trim());
			hbi.setExpireSec(Integer.parseInt(line.split("\\=")[1].trim()));
			al_hb.add(hbi);
		}
		br.close();
		fis.close();
	}

	private int checkHeartbeat(TrapInfo ti) {
		for (int i = 0; i < al_hb.size(); i++) {
			HeartbeatInfo hbi = al_hb.get(i);
			if (ti.getTrapPDU().get(OID_POSITION_IN_TRAP_V2).getVariable()
					.toString().equals(hbi.getOid()))
				return hbi.getExpireSec();
		}
		return -1;
	}

	public void initHeartbeat() throws Exception {
		getHearbeatConfig();// get heartbeat config
		hbh = new HeartBeatHandler(checkCycle, location, logger);
		hbh.Handler();
	}

}